package com.rgs.base.core.util.excel;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ReflectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.google.common.base.Strings;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.rgs.base.core.exception.BaseException;
import com.rgs.base.core.system.WebResponseState;
import com.rgs.base.core.util.DateFormat;
import com.rgs.base.core.util.excel.bo.ExcelConfigBO;
import com.rgs.base.core.util.excel.bo.ExcelDynamicHeadBO;
import com.rgs.base.core.util.excel.bo.FieldAnnotationBO;
import com.rgs.base.core.util.excel.bo.FieldExcelColumnBO;
import com.rgs.base.core.util.excel.bo.IExcelColumnBO;
import com.rgs.base.core.util.excel.bo.MapExcelColumnBO;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.xssf.usermodel.XSSFColor;

import java.awt.*;
import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author Ren Gaoshuai
 * @date 2022-03-08 15:45
 **/
@Slf4j
public final class ExcelUtil {

    private static final Pattern MATCHER = Pattern.compile("\\$\\{\\w{0,}\\}");


    //
    private static final LoadingCache<Class<?>, List<FieldExcelColumnBO>>
            FIELD_STR_EXCEL_COLUMN_LIST_MAP
            = CacheBuilder.newBuilder().build(
            new CacheLoader<Class<?>, List<FieldExcelColumnBO>>() {
                @Override
                public List<FieldExcelColumnBO> load(Class<?> key) throws Exception {
                    return getFieldAnnotationList(key).stream()
                            .flatMap(q -> q.getChildrenNameList().stream())
                            .peek(FieldExcelColumnBO::originBak)
                            .collect(Collectors.toList());
                }
            });


    private final static XSSFColor BORDER_COLOR = new XSSFColor(new Color(217, 217, 217));
    private final static XSSFColor HEADER_CELL_COLOR = new XSSFColor(new Color(226, 239, 218));
    private final static XSSFColor CELL_A_COLOR = new XSSFColor(new Color(242, 242, 242));
    private final static XSSFColor CELL_B_COLOR = new XSSFColor(new Color(255, 255, 255));


    private ExcelUtil() {

    }

    public static <T> ExcelWriter initExcelWriter(Class<T> tClass, File file) {
        return EasyExcel.write(file, tClass).build();
    }

    private static String headMapper(String headName, Map<String, String> titleMap) {
        return MATCHER.matcher(headName).matches() ?
                getHeadMapStr(headName.substring(2, headName.length() - 1), titleMap)
                : headName;
    }

    private static String getHeadMapStr(String headName, Map<String, String> titleMap) {
        String s = titleMap.get(headName);
        return s == null ? "" : s;
    }

    public static <T> WriteSheet initWriteSheet(Class<T> tClass, String titleName) {
        List<IExcelColumnBO> excelColumnList = getFieldStrExcelColumnList(tClass,

                new ExcelConfigBO());
        return ExcelUtil.initWriteSheet(excelColumnList, new ExcelConfigBO(titleName));
    }

    public static <T> void writeExcel(WriteSheet writeSheet, ExcelWriter excelWriter,
                                      List<T> tList, Class<T> tClass) {
        List<IExcelColumnBO> excelColumnList = getFieldStrExcelColumnList(tClass,
                new ExcelConfigBO());
        write2Excel(excelWriter, excelColumnList, writeSheet, new ArrayList<>(0), tList);

    }

    //Class<T> tClass,
    private static WriteSheet initWriteSheet(List<IExcelColumnBO> fieldStrExcelColumnList,
                                             ExcelConfigBO excelConfig) {
        String datetimeTitle = "制表时间:" + DateFormat.getStringDate(LocalDateTime.now());

        List<List<String>> head = fieldStrExcelColumnList.stream()
                .map(q -> {
                    List<String> columnNameList = q.getColumnMergeList();
                    List<String> headTitle = new ArrayList<>(columnNameList.size() + 2);
                    if (!Strings.isNullOrEmpty(excelConfig.getExportTitle())) {
                        headTitle.add(headMapper(excelConfig.getExportTitle(), excelConfig.getTitleMap()));
                    }
                    headTitle.add(datetimeTitle);
                    List<String> columnMergeStrTemp = columnNameList.stream()
                            .map(s -> headMapper(s, excelConfig.getTitleMap()))
                            .collect(Collectors.toList());

                    columnMergeStrTemp.add(headMapper(q.getColumnName(), excelConfig.getTitleMap()));
                    headTitle.addAll(columnMergeStrTemp);
                    return headTitle;
                }).collect(Collectors.toList());

        HorizontalCellPlusStyleStrategy horizontalCellStyleStrategy =
                new HorizontalCellPlusStyleStrategy(
                        headWriteCellStyle(),
                        Arrays.asList(contentWriteCellStyle(CELL_A_COLOR),
                                contentWriteCellStyle(CELL_B_COLOR)));

        return EasyExcel
                .writerSheet("Sheet0")
                .registerConverter(new LocalDateTimeConverter())
                .registerConverter(new LocalTimeConverter())
                .registerConverter(new LocalDateConverter())
                .head(head)
                .registerWriteHandler(new LongestMatchColumnWidthPlusStyleStrategy())
                .registerWriteHandler(horizontalCellStyleStrategy).build();
    }

    /**
     * @param file          写入文件对象
     * @param excelDataList 导入模型对象
     * @param tClass        类对象
     * @param total         总计行对象
     */
    public static <T> void writeExcel(File file,
                                      ExcelWriteIterator<T> excelDataList,
                                      Class<T> tClass,
                                      ExcelConfigBO excelConfig,
                                      List<T> total) {
        ExcelWriter excelWriter = null;
        try {
            //初始化方法
            excelWriter = ExcelUtil.initExcelWriter(tClass, file);

            List<IExcelColumnBO> fieldStrExcelColumnList =
                    getFieldStrExcelColumnList(tClass, excelConfig);

            List<IExcelColumnBO> fieldStrExcelColumnListNotNull = fieldStrExcelColumnList.stream().filter(q ->
                    !Strings.isNullOrEmpty(q.getColumnName()) && !"无".equals(q.getColumnName())
            ).collect(Collectors.toList());
            //
            List<IExcelColumnBO> noMapExcelColumnList = fieldStrExcelColumnListNotNull.stream().filter(q -> {
                if (q instanceof FieldExcelColumnBO) {
                    return !((FieldExcelColumnBO) q).getIsMapType();
                }
                return true;
            }).collect(Collectors.toList());


            WriteSheet writeSheet = ExcelUtil.initWriteSheet(noMapExcelColumnList, excelConfig);
            //
            List<FieldExcelColumnBO> fieldStrExcelColumnMapList
                    = fieldStrExcelColumnList.stream()
                    .filter(q -> q instanceof FieldExcelColumnBO)
                    .map(q -> (FieldExcelColumnBO) q)
                    .filter(FieldExcelColumnBO::getIsMapType)
                    .collect(Collectors.toList());
            //
            boolean flag = false;
            int i = 0;
            //重点
            while (excelDataList.hasNext()) {
                List<T> next = excelDataList.next();
                flag = i == 0 && next.size() == 0;
                //
                write2Excel(excelWriter, fieldStrExcelColumnListNotNull, writeSheet, fieldStrExcelColumnMapList, next);
                i++;
            }
            if (flag) {
                throw new BaseException("Excel模块", WebResponseState.FILE_EMPTY);
            } else {
                if (total != null) {
                    // 有合计数据
                    write2Excel(excelWriter, fieldStrExcelColumnListNotNull, writeSheet, fieldStrExcelColumnMapList, total);
                }
            }
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 数据写入excel
     *
     * @param excelWriter
     * @param fieldStrExcelColumnList
     * @param writeSheet
     * @param fieldStrExcelColumnMapList
     * @param next
     * @return void
     * @author daxiong
     * @date 2020/12/28 上午11:18
     */
    private static <T> void write2Excel(ExcelWriter excelWriter,
                                        List<IExcelColumnBO> fieldStrExcelColumnList,
                                        WriteSheet writeSheet,
                                        List<FieldExcelColumnBO> fieldStrExcelColumnMapList,
                                        List<T> next) {
        List<List<Object>> collectObject = next.stream()
                .map(p -> {
                    // Map兼容
                    Map<String, Object> collectMap = fieldStrExcelColumnMapList
                            .stream()
                            .map(mapTemp -> (Map<String, Object>) mapTemp.getValue(p))
                            .filter(mapTemp -> CollectionUtil.isNotEmpty(mapTemp))
                            .flatMap(mapTemp -> mapTemp.entrySet().stream())
                            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
                    return objectToListObject(p, fieldStrExcelColumnList, collectMap);
                })
                .collect(Collectors.toList());
        excelWriter.write(collectObject, writeSheet);
    }

    private static List<Object> objectToListObject(Object obj,
                                                   List<IExcelColumnBO> fieldStrExcelColumns,
                                                   Map<String, Object> collectMap) {
        return fieldStrExcelColumns.stream().map(q -> {
                    if (q instanceof FieldExcelColumnBO) {
                        return ((FieldExcelColumnBO) q).getValue(obj);
                    } else if (q instanceof MapExcelColumnBO) {
                        return collectMap.get(q.getFieldName());
                    }
                    return null;
                }
        ).collect(Collectors.toList());
    }

    private static List<FieldAnnotationBO> getFieldAnnotationList(Class<?> tClass) {
        List<FieldAnnotationBO> fieldAnnotationList = new ArrayList<>();
        Field[] fields = ReflectUtil.getFields(tClass);
        for (Field field : fields) {
            field.setAccessible(true);
            ExcelColumnAnnotation excelColumn = field.getDeclaredAnnotation(ExcelColumnAnnotation.class);
            FieldAnnotationBO fieldAnnotation;
            if (excelColumn != null) {
                fieldAnnotation = new FieldAnnotationBO(field, excelColumn);
                if (excelColumn.recursion()) {
                    fieldAnnotation.setChildren(ExcelUtil.getFieldAnnotationList(field.getType()));
                }
                fieldAnnotationList.add(fieldAnnotation);
            }
        }
        return fieldAnnotationList;
    }


    private static List<IExcelColumnBO> getFieldStrExcelColumnList(
            Class<?> tClass,
            ExcelConfigBO excelConfig) {
        List<FieldExcelColumnBO> fieldStrExcelColumnList =
                FIELD_STR_EXCEL_COLUMN_LIST_MAP.getUnchecked(tClass);


        fieldStrExcelColumnList = fieldStrExcelColumnList.stream()
                .filter(q -> !q.isIgnore(excelConfig.getIgnoreSet()))
                //动态排序字段计算
                .peek(q ->
                        q.unionExcelDynamicHead(excelConfig.getDynamicHeadMap().getOrDefault(q.getFieldName(),
                                ExcelDynamicHeadBO.DEF_EXCEL_DYNAMIC_HEAD)
                        )
                )
                .collect(Collectors.toList());

        List<MapExcelColumnBO> mapExcelColumnList = excelConfig.getDynamicHeadMap()
                .entrySet().stream()
                .filter(q -> q.getValue().getIsMap())
                .map(q -> new MapExcelColumnBO(q.getValue(), q.getKey())).collect(Collectors.toList());


        List<IExcelColumnBO> excelColumnList = new ArrayList<>(fieldStrExcelColumnList.size() + mapExcelColumnList.size());
        excelColumnList.addAll(fieldStrExcelColumnList);
        excelColumnList.addAll(mapExcelColumnList);

        return excelColumnList.stream()
                .sorted(Comparator.comparingInt(IExcelColumnBO::getOrderNumber))
                .collect(Collectors.toList());
    }

    private static void setBorder(WriteCellPlusStyle cellStyle) {
        // 垂直对齐
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        //
        cellStyle.setBorderRight(BorderStyle.THIN);
        cellStyle.setRightBorderColor(BORDER_COLOR.getIndex());
        cellStyle.setBorderLeft(BorderStyle.THIN);
        cellStyle.setLeftBorderColor(BORDER_COLOR.getIndex());
        cellStyle.setBorderTop(BorderStyle.THIN);
        cellStyle.setTopBorderColor(BORDER_COLOR.getIndex());
        cellStyle.setBorderBottom(BorderStyle.THIN);
        cellStyle.setBottomBorderColor(BORDER_COLOR.getIndex());
    }

    private static WriteCellPlusStyle headWriteCellStyle() {
        WriteCellPlusStyle writeCellStyle = new WriteCellPlusStyle();
        setBorder(writeCellStyle);
        // 前景色
        writeCellStyle.setFillForegroundXSSFColor(HEADER_CELL_COLOR);
        // 颜色填充方式
        writeCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontHeightInPoints((short) 12);
        headWriteFont.setColor(IndexedColors.BLACK.getIndex());
        headWriteFont.setFontName("微软雅黑");
        writeCellStyle.setWriteFont(headWriteFont);
        return writeCellStyle;
    }

    private static WriteCellPlusStyle contentWriteCellStyle(XSSFColor xssfColor) {
        // 内容的策略
        WriteCellPlusStyle contentWriteCellStyle = new WriteCellPlusStyle();
        setBorder(contentWriteCellStyle);
        contentWriteCellStyle.setFillForegroundXSSFColor(xssfColor);
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        contentWriteCellStyle.setShrinkToFit(true);
        WriteFont contentWriteFont = new WriteFont();
        // 字体大小
        contentWriteFont.setFontHeightInPoints((short) 12);
        contentWriteFont.setColor(IndexedColors.BLACK.getIndex());
        contentWriteFont.setFontName("微软雅黑");
        contentWriteCellStyle.setWriteFont(contentWriteFont);
        return contentWriteCellStyle;
    }

    public static <T> void readExcel(InputStream inputStream,
                                     Class<T> tClass,
                                     AnalysisEventListener<T> analysisEventListener) {
        //2022/02/22修改 两行头标题（从第三行开始读取数据）
        EasyExcel.read(inputStream, tClass, analysisEventListener).sheet().headRowNumber(2).doRead();
    }

    public static void fillExcel(File excelFile, File templateFile,
                                 Map<String, String> staticMap) {
        ExcelWriter excelWriter = EasyExcel.write(excelFile).
                withTemplate(templateFile).build();
        WriteSheet writeSheet = EasyExcel.writerSheet().
                registerWriteHandler(new FillRowWriteHandler()).
                build();
        excelWriter.fill(staticMap, writeSheet);
        excelWriter.finish();
    }
}

